home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / lisp / stk-3.002 / stk-3 / STk-3.1 / Tk / generic / tkTextIndex.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-31  |  21.1 KB  |  862 lines

  1. /* 
  2.  * tkTextIndex.c --
  3.  *
  4.  *    This module provides procedures that manipulate indices for
  5.  *    text widgets.
  6.  *
  7.  * Copyright (c) 1992-1994 The Regents of the University of California.
  8.  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
  9.  *
  10.  * See the file "license.terms" for information on usage and redistribution
  11.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  12.  *
  13.  * SCCS: @(#) tkTextIndex.c 1.13 96/02/15 18:52:57
  14.  */
  15.  
  16. #include "default.h"
  17. #include "tkPort.h"
  18. #include "tkInt.h"
  19. #include "tkText.h"
  20.  
  21. /*
  22.  * Index to use to select last character in line (very large integer):
  23.  */
  24.  
  25. #define LAST_CHAR 1000000
  26.  
  27. /*
  28.  * Forward declarations for procedures defined later in this file:
  29.  */
  30.  
  31. static char *        ForwBack _ANSI_ARGS_((char *string,
  32.                 TkTextIndex *indexPtr));
  33. static char *        StartEnd _ANSI_ARGS_(( char *string,
  34.                 TkTextIndex *indexPtr));
  35.  
  36. /*
  37.  *--------------------------------------------------------------
  38.  *
  39.  * TkTextMakeIndex --
  40.  *
  41.  *    Given a line index and a character index, look things up
  42.  *    in the B-tree and fill in a TkTextIndex structure.
  43.  *
  44.  * Results:
  45.  *    The structure at *indexPtr is filled in with information
  46.  *    about the character at lineIndex and charIndex (or the
  47.  *    closest existing character, if the specified one doesn't
  48.  *    exist), and indexPtr is returned as result.
  49.  *
  50.  * Side effects:
  51.  *    None.
  52.  *
  53.  *--------------------------------------------------------------
  54.  */
  55.  
  56. TkTextIndex *
  57. TkTextMakeIndex(tree, lineIndex, charIndex, indexPtr)
  58.     TkTextBTree tree;        /* Tree that lineIndex and charIndex refer
  59.                  * to. */
  60.     int lineIndex;        /* Index of desired line (0 means first
  61.                  * line of text). */
  62.     int charIndex;        /* Index of desired character. */
  63.     TkTextIndex *indexPtr;    /* Structure to fill in. */
  64. {
  65.     register TkTextSegment *segPtr;
  66.     int index;
  67.  
  68.     indexPtr->tree = tree;
  69.     if (lineIndex < 0) {
  70.     lineIndex = 0;
  71.     charIndex = 0;
  72.     }
  73.     if (charIndex < 0) {
  74.     charIndex = 0;
  75.     }
  76.     indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex);
  77.     if (indexPtr->linePtr == NULL) {
  78.     indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree));
  79.     charIndex = 0;
  80.     }
  81.  
  82.     /*
  83.      * Verify that the index is within the range of the line.
  84.      * If not, just use the index of the last character in the line.
  85.      */
  86.  
  87.     for (index = 0, segPtr = indexPtr->linePtr->segPtr; ;
  88.         segPtr = segPtr->nextPtr) {
  89.     if (segPtr == NULL) {
  90.         indexPtr->charIndex = index-1;
  91.         break;
  92.     }
  93.     index += segPtr->size;
  94.     if (index > charIndex) {
  95.         indexPtr->charIndex = charIndex;
  96.         break;
  97.     }
  98.     }
  99.     return indexPtr;
  100. }
  101.  
  102. /*
  103.  *--------------------------------------------------------------
  104.  *
  105.  * TkTextIndexToSeg --
  106.  *
  107.  *    Given an index, this procedure returns the segment and
  108.  *    offset within segment for the index.
  109.  *
  110.  * Results:
  111.  *    The return value is a pointer to the segment referred to
  112.  *    by indexPtr;  this will always be a segment with non-zero
  113.  *    size.  The variable at *offsetPtr is set to hold the
  114.  *    integer offset within the segment of the character
  115.  *    given by indexPtr.
  116.  *
  117.  * Side effects:
  118.  *    None.
  119.  *
  120.  *--------------------------------------------------------------
  121.  */
  122.  
  123. TkTextSegment *
  124. TkTextIndexToSeg(indexPtr, offsetPtr)
  125.     TkTextIndex *indexPtr;        /* Text index. */
  126.     int *offsetPtr;            /* Where to store offset within
  127.                      * segment, or NULL if offset isn't
  128.                      * wanted. */
  129. {
  130.     register TkTextSegment *segPtr;
  131.     int offset;
  132.  
  133.     for (offset = indexPtr->charIndex, segPtr = indexPtr->linePtr->segPtr;
  134.         offset >= segPtr->size;
  135.         offset -= segPtr->size, segPtr = segPtr->nextPtr) {
  136.     /* Empty loop body. */
  137.     }
  138.     if (offsetPtr != NULL) {
  139.     *offsetPtr = offset;
  140.     }
  141.     return segPtr;
  142. }
  143.  
  144. /*
  145.  *--------------------------------------------------------------
  146.  *
  147.  * TkTextSegToOffset --
  148.  *
  149.  *    Given a segment pointer and the line containing it, this
  150.  *    procedure returns the offset of the segment within its
  151.  *    line.
  152.  *
  153.  * Results:
  154.  *    The return value is the offset (within its line) of the
  155.  *    first character in segPtr.
  156.  *
  157.  * Side effects:
  158.  *    None.
  159.  *
  160.  *--------------------------------------------------------------
  161.  */
  162.  
  163. int
  164. TkTextSegToOffset(segPtr, linePtr)
  165.     TkTextSegment *segPtr;        /* Segment whose offset is desired. */
  166.     TkTextLine *linePtr;        /* Line containing segPtr. */
  167. {
  168.     TkTextSegment *segPtr2;
  169.     int offset;
  170.  
  171.     offset = 0;
  172.     for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr;
  173.         segPtr2 = segPtr2->nextPtr) {
  174.     offset += segPtr2->size;
  175.     }
  176.     return offset;
  177. }
  178.  
  179. /*
  180.  *----------------------------------------------------------------------
  181.  *
  182.  * TkTextGetIndex --
  183.  *
  184.  *    Given a string, return the line and character indices that
  185.  *    it describes.
  186.  *
  187.  * Results:
  188.  *    The return value is a standard Tcl return result.  If
  189.  *    TCL_OK is returned, then everything went well and the index
  190.  *    at *indexPtr is filled in;  otherwise TCL_ERROR is returned
  191.  *    and an error message is left in interp->result.
  192.  *
  193.  * Side effects:
  194.  *    None.
  195.  *
  196.  *----------------------------------------------------------------------
  197.  */
  198.  
  199. int
  200. TkTextGetIndex(interp, textPtr, string, indexPtr)
  201.     Tcl_Interp *interp;        /* Use this for error reporting. */
  202.     TkText *textPtr;        /* Information about text widget. */
  203.     char *string;        /* Textual description of position. */
  204.     TkTextIndex *indexPtr;    /* Index structure to fill in. */
  205. {
  206.     register char *p;
  207.     char *end, *endOfBase;
  208.     Tcl_HashEntry *hPtr;
  209.     TkTextTag *tagPtr;
  210.     TkTextSearch search;
  211.     TkTextIndex first, last;
  212.     int wantLast, result;
  213.     char c;
  214.  
  215.     /*
  216.      *---------------------------------------------------------------------
  217.      * Stage 1: check to see if the index consists of nothing but a mar
  218.      * name.  We do this check now even though it's also done later, in
  219.      * order to allow mark names that include funny characters such as
  220.      * spaces or "+1c".
  221.      *---------------------------------------------------------------------
  222.      */
  223.  
  224.     if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) {
  225.     return TCL_OK;
  226.     }
  227.  
  228.     /*
  229.      *------------------------------------------------
  230.      * Stage 2: start again by parsing the base index.
  231.      *------------------------------------------------
  232.      */
  233.  
  234.     indexPtr->tree = textPtr->tree;
  235.  
  236.     /*
  237.      * First look for the form "tag.first" or "tag.last" where "tag"
  238.      * is the name of a valid tag.  Try to use up as much as possible
  239.      * of the string in this check (strrchr instead of strchr below).
  240.      * Doing the check now, and in this way, allows tag names to include
  241.      * funny characters like "@" or "+1c".
  242.      */
  243.  
  244.     p = strrchr(string, '.');
  245.     if (p != NULL) {
  246.     if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) {
  247.         wantLast = 0;
  248.         endOfBase = p+6;
  249.     } else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) {
  250.         wantLast = 1;
  251.         endOfBase = p+5;
  252.     } else {
  253.         goto tryxy;
  254.     }
  255.     *p = 0;
  256.     hPtr = Tcl_FindHashEntry(&textPtr->tagTable, string);
  257.     *p = '.';
  258.     if (hPtr == NULL) {
  259.         goto tryxy;
  260.     }
  261.     tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  262.     TkTextMakeIndex(textPtr->tree, 0, 0, &first);
  263.     TkTextMakeIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0,
  264.         &last);
  265.     TkBTreeStartSearch(&first, &last, tagPtr, &search);
  266.     if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) {
  267.         Tcl_AppendResult(interp,
  268.             "text doesn't contain any characters tagged with \"",
  269.             Tcl_GetHashKey(&textPtr->tagTable, hPtr), "\"",
  270.                 (char *) NULL);
  271.         return TCL_ERROR;
  272.     }
  273.     *indexPtr = search.curIndex;
  274.     if (wantLast) {
  275.         while (TkBTreeNextTag(&search)) {
  276.         *indexPtr = search.curIndex;
  277.         }
  278.     }
  279.     goto gotBase;
  280.     }
  281.  
  282.     tryxy:
  283.     if (string[0] == '@') {
  284.     /*
  285.      * Find character at a given x,y location in the window.
  286.      */
  287.  
  288.     int x, y;
  289.  
  290.     p = string+1;
  291.     x = strtol(p, &end, 0);
  292.     if ((end == p) || (*end != ',')) {
  293.         goto error;
  294.     }
  295.     p = end+1;
  296.     y = strtol(p, &end, 0);
  297.     if (end == p) {
  298.         goto error;
  299.     }
  300.     TkTextPixelIndex(textPtr, x, y, indexPtr);
  301.     endOfBase = end;
  302.     goto gotBase; 
  303.     }
  304. #ifdef STk_CODE
  305.     if (string[0] == '(' && isdigit(UCHAR(string[1])) || (string[1] == '-')) {
  306.     int lineIndex, charIndex;
  307.  
  308.     /*
  309.      * Base is identified as a Scheme pair
  310.      */
  311.  
  312.     lineIndex = strtol(string+1, &end, 0) - 1;
  313.     if ((end == string+1) || (strncmp(end, " . ", 3) != 0)) {
  314.         goto error;
  315.     }
  316.     p = end+3; string = 0;
  317.     if ((*p == 'e')   && (strncmp(p, "end)", 4) == 0)) {
  318.         charIndex = LAST_CHAR;
  319.         endOfBase = p+4;
  320.     } else if ((*p == '\"')  && (strncmp(p, "\"end\")", 6) == 0)) {
  321.         charIndex = LAST_CHAR;
  322.         endOfBase = p+6;
  323.     } else {
  324.         charIndex = strtol(p, &end, 0);
  325.         if (end == p || *end != ')') {
  326.         goto error;
  327.         }
  328.         endOfBase = end+1;
  329.     }
  330.     TkTextMakeIndex(textPtr->tree, lineIndex, charIndex, indexPtr);
  331.     goto gotBase;
  332.     }
  333. #endif
  334.     if (isdigit(UCHAR(string[0])) || (string[0] == '-')) {
  335.     int lineIndex, charIndex;
  336.  
  337.     /*
  338.      * Base is identified with line and character indices.
  339.      */
  340.  
  341.     lineIndex = strtol(string, &end, 0) - 1;
  342.     if ((end == string) || (*end != '.')) {
  343.         goto error;
  344.     }
  345.     p = end+1;
  346.     if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) {
  347.         charIndex = LAST_CHAR;
  348.         endOfBase = p+3;
  349.     } else {
  350.         charIndex = strtol(p, &end, 0);
  351.         if (end == p) {
  352.         goto error;
  353.         }
  354.         endOfBase = end;
  355.     }
  356.     TkTextMakeIndex(textPtr->tree, lineIndex, charIndex, indexPtr);
  357.     goto gotBase;
  358.     }
  359.  
  360.     for (p = string; *p != 0; p++) {
  361.     if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) {
  362.         break;
  363.     }
  364.     }
  365.     endOfBase = p;
  366.     if (string[0] == '.') {
  367.     /*
  368.      * See if the base position is the name of an embedded window.
  369.      */
  370.  
  371.     c = *endOfBase;
  372.     *endOfBase = 0;
  373.     result = TkTextWindowIndex(textPtr, string, indexPtr);
  374.     *endOfBase = c;
  375.     if (result != 0) {
  376.         goto gotBase;
  377.     }
  378.     }
  379.     if ((string[0] == 'e')
  380.         && (strncmp(string, "end", (size_t) (endOfBase-string)) == 0)) {
  381.     /*
  382.      * Base position is end of text.
  383.      */
  384.  
  385.     TkTextMakeIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree),
  386.         0, indexPtr);
  387.     goto gotBase;
  388.     } else {
  389.     /*
  390.      * See if the base position is the name of a mark.
  391.      */
  392.  
  393.     c = *endOfBase;
  394.     *endOfBase = 0;
  395.     result = TkTextMarkNameToIndex(textPtr, string, indexPtr);
  396.     *endOfBase = c;
  397.     if (result == TCL_OK) {
  398.         goto gotBase;
  399.     }
  400.     }
  401.     goto error;
  402.  
  403.     /*
  404.      *-------------------------------------------------------------------
  405.      * Stage 3: process zero or more modifiers.  Each modifier is either
  406.      * a keyword like "wordend" or "linestart", or it has the form
  407.      * "op count units" where op is + or -, count is a number, and units
  408.      * is "chars" or "lines".
  409.      *-------------------------------------------------------------------
  410.      */
  411.  
  412.     gotBase:
  413.     p = endOfBase;
  414.     while (1) {
  415.     while (isspace(UCHAR(*p))) {
  416.         p++;
  417.     }
  418.     if (*p == 0) {
  419.         break;
  420.     }
  421.     
  422.     if ((*p == '+') || (*p == '-')) {
  423.         p = ForwBack(p, indexPtr);
  424.     } else {
  425.         p = StartEnd(p, indexPtr);
  426.     }
  427.     if (p == NULL) {
  428.         goto error;
  429.     }
  430.     }
  431.     return TCL_OK;
  432.  
  433.     error:
  434.     Tcl_AppendResult(interp, "bad text index \"", string, "\"",
  435.         (char *) NULL);
  436.     return TCL_ERROR;
  437. }
  438.  
  439. /*
  440.  *----------------------------------------------------------------------
  441.  *
  442.  * TkTextPrintIndex --
  443.  *
  444.  *    
  445.  *    This procedure generates a string description of an index,
  446.  *    suitable for reading in again later.
  447.  *
  448.  * Results:
  449.  *    The characters pointed to by string are modified.
  450.  *
  451.  * Side effects:
  452.  *    None.
  453.  *
  454.  *----------------------------------------------------------------------
  455.  */
  456.  
  457. void
  458. TkTextPrintIndex(indexPtr, string)
  459.     TkTextIndex *indexPtr;    /* Pointer to index. */
  460.     char *string;        /* Place to store the position.  Must have
  461.                  * at least TK_POS_CHARS characters. */
  462. {
  463. #ifdef STk_CODE
  464.     sprintf(string, "(%d . %d)", TkBTreeLineIndex(indexPtr->linePtr) + 1,
  465. #else
  466.     sprintf(string, "%d.%d", TkBTreeLineIndex(indexPtr->linePtr) + 1,
  467. #endif
  468.         indexPtr->charIndex);
  469. }
  470.  
  471. /*
  472.  *--------------------------------------------------------------
  473.  *
  474.  * TkTextIndexCmp --
  475.  *
  476.  *    Compare two indices to see which one is earlier in
  477.  *    the text.
  478.  *
  479.  * Results:
  480.  *    The return value is 0 if index1Ptr and index2Ptr refer
  481.  *    to the same position in the file, -1 if index1Ptr refers
  482.  *    to an earlier position than index2Ptr, and 1 otherwise.
  483.  *
  484.  * Side effects:
  485.  *    None.
  486.  *
  487.  *--------------------------------------------------------------
  488.  */
  489.  
  490. int
  491. TkTextIndexCmp(index1Ptr, index2Ptr)
  492.     TkTextIndex *index1Ptr;        /* First index. */
  493.     TkTextIndex *index2Ptr;        /* Second index. */
  494. {
  495.     int line1, line2;
  496.  
  497.     if (index1Ptr->linePtr == index2Ptr->linePtr) {
  498.     if (index1Ptr->charIndex < index2Ptr->charIndex) {
  499.         return -1;
  500.     } else if (index1Ptr->charIndex > index2Ptr->charIndex) {
  501.         return 1;
  502.     } else {
  503.         return 0;
  504.     }
  505.     }
  506.     line1 = TkBTreeLineIndex(index1Ptr->linePtr);
  507.     line2 = TkBTreeLineIndex(index2Ptr->linePtr);
  508.     if (line1 < line2) {
  509.     return -1;
  510.     }
  511.     if (line1 > line2) {
  512.     return 1;
  513.     }
  514.     return 0;
  515. }
  516.  
  517. /*
  518.  *----------------------------------------------------------------------
  519.  *
  520.  * ForwBack --
  521.  *
  522.  *    This procedure handles +/- modifiers for indices to adjust
  523.  *    the index forwards or backwards.
  524.  *
  525.  * Results:
  526.  *    If the modifier in string is successfully parsed then the
  527.  *    return value is the address of the first character after the
  528.  *    modifier, and *indexPtr is updated to reflect the modifier.
  529.  *    If there is a syntax error in the modifier then NULL is returned.
  530.  *
  531.  * Side effects:
  532.  *    None.
  533.  *
  534.  *----------------------------------------------------------------------
  535.  */
  536.  
  537. static char *
  538. ForwBack(string, indexPtr)
  539.     char *string;        /* String to parse for additional info
  540.                  * about modifier (count and units). 
  541.                  * Points to "+" or "-" that starts
  542.                  * modifier. */
  543.     TkTextIndex *indexPtr;    /* Index to update as specified in string. */
  544. {
  545.     register char *p;
  546.     char *end, *units;
  547.     int count, lineIndex;
  548.     size_t length;
  549.  
  550.     /*
  551.      * Get the count (how many units forward or backward).
  552.      */
  553.  
  554.     p = string+1;
  555.     while (isspace(UCHAR(*p))) {
  556.     p++;
  557.     }
  558.     count = strtol(p, &end, 0);
  559.     if (end == p) {
  560.     return NULL;
  561.     }
  562.     p = end;
  563.     while (isspace(UCHAR(*p))) {
  564.     p++;
  565.     }
  566.  
  567.     /*
  568.      * Find the end of this modifier (next space or + or - character),
  569.      * then parse the unit specifier and update the position
  570.      * accordingly.
  571.      */
  572.  
  573.     units = p; 
  574.     while ((*p != 0) && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) {
  575.     p++;
  576.     }
  577.     length = p - units;
  578.     if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) {
  579.     if (*string == '+') {
  580.         TkTextIndexForwChars(indexPtr, count, indexPtr);
  581.     } else {
  582.         TkTextIndexBackChars(indexPtr, count, indexPtr);
  583.     }
  584.     } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
  585.     lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
  586.     if (*string == '+') {
  587.         lineIndex += count;
  588.     } else {
  589.         lineIndex -= count;
  590.  
  591.         /*
  592.          * The check below retains the character position, even
  593.          * if the line runs off the start of the file.  Without
  594.          * it, the character position will get reset to 0 by
  595.          * TkTextMakeIndex.
  596.          */
  597.  
  598.         if (lineIndex < 0) {
  599.         lineIndex = 0;
  600.         }
  601.     }
  602.     TkTextMakeIndex(indexPtr->tree, lineIndex, indexPtr->charIndex,
  603.         indexPtr);
  604.     } else {
  605.     return NULL;
  606.     }
  607.     return p;
  608. }
  609.  
  610. /*
  611.  *----------------------------------------------------------------------
  612.  *
  613.  * TkTextIndexForwChars --
  614.  *
  615.  *    Given an index for a text widget, this procedure creates a
  616.  *    new index that points "count" characters ahead of the source
  617.  *    index.
  618.  *
  619.  * Results:
  620.  *    *dstPtr is modified to refer to the character "count" characters
  621.  *    after srcPtr, or to the last character in the file if there aren't
  622.  *    "count" characters left in the file.
  623.  *
  624.  * Side effects:
  625.  *    None.
  626.  *
  627.  *----------------------------------------------------------------------
  628.  */
  629.  
  630.     /* ARGSUSED */
  631. void
  632. TkTextIndexForwChars(srcPtr, count, dstPtr)
  633.     TkTextIndex *srcPtr;        /* Source index. */
  634.     int count;                /* How many characters forward to
  635.                      * move.  May be negative. */
  636.     TkTextIndex *dstPtr;        /* Destination index: gets modified. */
  637. {
  638.     TkTextLine *linePtr;
  639.     TkTextSegment *segPtr;
  640.     int lineLength;
  641.  
  642.     if (count < 0) {
  643.     TkTextIndexBackChars(srcPtr, -count, dstPtr);
  644.     return;
  645.     }
  646.  
  647.     *dstPtr = *srcPtr;
  648.     dstPtr->charIndex += count;
  649.     while (1) {
  650.     /*
  651.      * Compute the length of the current line.
  652.      */
  653.  
  654.     lineLength = 0;
  655.     for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
  656.         segPtr = segPtr->nextPtr) {
  657.         lineLength += segPtr->size;
  658.     }
  659.  
  660.     /*
  661.      * If the new index is in the same line then we're done.
  662.      * Otherwise go on to the next line.
  663.      */
  664.  
  665.     if (dstPtr->charIndex < lineLength) {
  666.         return;
  667.     }
  668.     dstPtr->charIndex -= lineLength;
  669.     linePtr = TkBTreeNextLine(dstPtr->linePtr);
  670.     if (linePtr == NULL) {
  671.         dstPtr->charIndex = lineLength - 1;
  672.         return;
  673.     }
  674.     dstPtr->linePtr = linePtr;
  675.     }
  676. }
  677.  
  678. /*
  679.  *----------------------------------------------------------------------
  680.  *
  681.  * TkTextIndexBackChars --
  682.  *
  683.  *    Given an index for a text widget, this procedure creates a
  684.  *    new index that points "count" characters earlier than the
  685.  *    source index.
  686.  *
  687.  * Results:
  688.  *    *dstPtr is modified to refer to the character "count" characters
  689.  *    before srcPtr, or to the first character in the file if there aren't
  690.  *    "count" characters earlier than srcPtr.
  691.  *
  692.  * Side effects:
  693.  *    None.
  694.  *
  695.  *----------------------------------------------------------------------
  696.  */
  697.  
  698. void
  699. TkTextIndexBackChars(srcPtr, count, dstPtr)
  700.     TkTextIndex *srcPtr;        /* Source index. */
  701.     int count;                /* How many characters backward to
  702.                      * move.  May be negative. */
  703.     TkTextIndex *dstPtr;        /* Destination index: gets modified. */
  704. {
  705.     TkTextSegment *segPtr;
  706.     int lineIndex;
  707.  
  708.     if (count < 0) {
  709.     TkTextIndexForwChars(srcPtr, -count, dstPtr);
  710.     return;
  711.     }
  712.  
  713.     *dstPtr = *srcPtr;
  714.     dstPtr->charIndex -= count;
  715.     lineIndex = -1;
  716.     while (dstPtr->charIndex < 0) {
  717.     /*
  718.      * Move back one line in the text.  If we run off the beginning
  719.      * of the file then just return the first character in the text.
  720.      */
  721.  
  722.     if (lineIndex < 0) {
  723.         lineIndex = TkBTreeLineIndex(dstPtr->linePtr);
  724.     }
  725.     if (lineIndex == 0) {
  726.         dstPtr->charIndex = 0;
  727.         return;
  728.     }
  729.     lineIndex--;
  730.     dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex);
  731.  
  732.     /*
  733.      * Compute the length of the line and add that to dstPtr->charIndex.
  734.      */
  735.  
  736.     for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
  737.         segPtr = segPtr->nextPtr) {
  738.         dstPtr->charIndex += segPtr->size;
  739.     }
  740.     }
  741. }
  742.  
  743. /*
  744.  *----------------------------------------------------------------------
  745.  *
  746.  * StartEnd --
  747.  *
  748.  *    This procedure handles modifiers like "wordstart" and "lineend"
  749.  *    to adjust indices forwards or backwards.
  750.  *
  751.  * Results:
  752.  *    If the modifier is successfully parsed then the return value
  753.  *    is the address of the first character after the modifier, and
  754.  *    *indexPtr is updated to reflect the modifier. If there is a
  755.  *    syntax error in the modifier then NULL is returned.
  756.  *
  757.  * Side effects:
  758.  *    None.
  759.  *
  760.  *----------------------------------------------------------------------
  761.  */
  762.  
  763. static char *
  764. StartEnd(string, indexPtr)
  765.     char *string;        /* String to parse for additional info
  766.                  * about modifier (count and units). 
  767.                  * Points to first character of modifer
  768.                  * word. */
  769.     TkTextIndex *indexPtr;    /* Index to mdoify based on string. */
  770. {
  771.     char *p;
  772.     int c, offset;
  773.     size_t length;
  774.     register TkTextSegment *segPtr;
  775.  
  776.     /*
  777.      * Find the end of the modifier word.
  778.      */
  779.  
  780.     for (p = string; isalnum(UCHAR(*p)); p++) {
  781.     /* Empty loop body. */
  782.     }
  783.     length = p-string;
  784.     if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
  785.         && (length >= 5)) {
  786.     indexPtr->charIndex = 0;
  787.     for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL;
  788.         segPtr = segPtr->nextPtr) {
  789.         indexPtr->charIndex += segPtr->size;
  790.     }
  791.     indexPtr->charIndex -= 1;
  792.     } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
  793.         && (length >= 5)) {
  794.     indexPtr->charIndex = 0;
  795.     } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
  796.         && (length >= 5)) {
  797.     int firstChar = 1;
  798.  
  799.     /*
  800.      * If the current character isn't part of a word then just move
  801.      * forward one character.  Otherwise move forward until finding
  802.      * a character that isn't part of a word and stop there.
  803.      */
  804.  
  805.     segPtr = TkTextIndexToSeg(indexPtr, &offset);
  806.     while (1) {
  807.         if (segPtr->typePtr == &tkTextCharType) {
  808.         c = segPtr->body.chars[offset];
  809.         if (!isalnum(UCHAR(c)) && (c != '_')) {
  810.             break;
  811.         }
  812.         firstChar = 0;
  813.         }
  814.         offset += 1;
  815.         indexPtr->charIndex += 1;
  816.         if (offset >= segPtr->size) {
  817.         segPtr = TkTextIndexToSeg(indexPtr, &offset);
  818.         }
  819.     }
  820.     if (firstChar) {
  821.         TkTextIndexForwChars(indexPtr, 1, indexPtr);
  822.     }
  823.     } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
  824.         && (length >= 5)) {
  825.     int firstChar = 1;
  826.  
  827.     /*
  828.      * Starting with the current character, look for one that's not
  829.      * part of a word and keep moving backward until you find one.
  830.      * Then if the character found wasn't the first one, move forward
  831.      * again one position.
  832.      */
  833.  
  834.     segPtr = TkTextIndexToSeg(indexPtr, &offset);
  835.     while (1) {
  836.         if (segPtr->typePtr == &tkTextCharType) {
  837.         c = segPtr->body.chars[offset];
  838.         if (!isalnum(UCHAR(c)) && (c != '_')) {
  839.             break;
  840.         }
  841.         firstChar = 0;
  842.         }
  843.         offset -= 1;
  844.         indexPtr->charIndex -= 1;
  845.         if (offset < 0) {
  846.         if (indexPtr->charIndex < 0) {
  847.             indexPtr->charIndex = 0;
  848.             goto done;
  849.         }
  850.         segPtr = TkTextIndexToSeg(indexPtr, &offset);
  851.         }
  852.     }
  853.     if (!firstChar) {
  854.         TkTextIndexForwChars(indexPtr, 1, indexPtr);
  855.     }
  856.     } else {
  857.     return NULL;
  858.     }
  859.     done:
  860.     return p;
  861. }
  862.